/*-
 * Generates code for the data.
 *
 * Copyright (c) 2010 Alex Suhan
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer
 * in this position and unchanged.
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import java.util.Vector;
import java.util.Collections;

class DataSection
{
    private LayoutBuilder ctx;
    
    DataSection(LayoutBuilder ctx)
    {
        this.ctx = ctx;
    }
    
    private static void emitDefWord(int val, java.io.PrintStream printer)
    {
        printer.println("\t" + IRLingo.defineWord + "\t" + val);
    }
    
    private static void emitDefLabel(String label, java.io.PrintStream printer)
    {
        printer.println("\t" + IRLingo.defineLabel + "\t" + label);
    }
    
    private static void emitDefStorage(int size, java.io.PrintStream printer)
    {
        printer.println("\t" + IRLingo.defineStorage + "\t" + size);
    }
    
    public static void printEscapedString(String str, java.io.PrintStream printer)
    {
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            switch (c) {
                case '\\':
                    printer.print("\\\\");
                    break;
                case '\"':
                    printer.print("\\\"");
                    break;
                case '\n':
                    printer.print("\\n");
                    break;
                case '\t':
                    printer.print("\\t");
                    break;
                case '\b':
                    printer.print("\\b");
                    break;
                case '\f':
                    printer.print("\\f");
                    break;
                default:
                    if (c >= 0x20 && c <= 0x7f) {
                        printer.print(c);
                    }
                    else {
                        String octal = Integer.toOctalString(c);
                        printer.print('\\');
                        switch (octal.length()) {
                            case 1:
                                printer.print('0');
                            case 2:
                                printer.print('0');
                            default:
                                printer.print(octal);
                    }
                }
            }
        }
    }
    
    private static void emitRawString(String str, java.io.PrintStream printer)
    {
        printer.print("\t" + IRLingo.defineBytes + "\t\"");
        printEscapedString(str, printer);
        printer.println("\"");
        emitDefStorage(4 - str.length() % 4, printer);
    }
    
    private static void emitStringConstant(String str, java.io.PrintStream printer)
    {
        int idx = ScalaTreeChecker.stringTable.get(str);
        printer.println(CgenConstants.strConstPrefix + (idx + ":"));
        emitDefWord(CgenConstants.STRING_TAG, printer);
        emitDefWord(CgenConstants.DEFAULT_OBJFIELDS + CgenConstants.STRING_SLOTS +
                    (str.length() + 4) / 4, printer);
        emitDefLabel(ScalaConstants.StringClassname + IRLingo.dispatchSuffix, printer);
        String label =
            CgenConstants.intConstPrefix + ScalaTreeChecker.integerTable.get(str.length());
        emitDefLabel(label, printer);
        emitRawString(str, printer);
    }
    
    private static void emitIntegerConstant(Integer value, java.io.PrintStream printer)
    {
        int idx = ScalaTreeChecker.integerTable.get(value);
        printer.println(CgenConstants.intConstPrefix + (idx + ":"));
        emitDefWord(CgenConstants.INTEGER_TAG, printer);
        emitDefWord(CgenConstants.DEFAULT_OBJFIELDS + CgenConstants.INT_SLOTS, printer);
        emitDefLabel(ScalaConstants.ObjectClassname + IRLingo.dispatchSuffix, printer);
        emitDefWord(value, printer);
    }
    
    private static void emitBoolConstant(boolean value, java.io.PrintStream printer)
    {
        printer.println(CgenConstants.boolConstPrefix + (value ? 1 : 0) + ":");
        emitDefWord(CgenConstants.BOOLEAN_TAG, printer);
        emitDefWord(CgenConstants.DEFAULT_OBJFIELDS + CgenConstants.BOOL_SLOTS, printer);
        emitDefLabel(ScalaConstants.ObjectClassname + IRLingo.dispatchSuffix, printer);
        emitDefWord(value ? 1 : 0, printer);
    }
    
    private static void emitObjectPrototype(java.io.PrintStream printer)
    {
        printer.println(ScalaConstants.ObjectClassname + IRLingo.protoSuffix + ":");
        emitDefWord(CgenConstants.OBJECT_TAG, printer);
        emitDefWord(CgenConstants.DEFAULT_OBJFIELDS, printer);
        emitDefLabel(ScalaConstants.ObjectClassname + IRLingo.dispatchSuffix, printer);
    }
    
    private static void emitIntegerPrototype(java.io.PrintStream printer)
    {
        printer.println(ScalaConstants.IntegerClassname + IRLingo.protoSuffix + ":");
        emitDefWord(CgenConstants.INTEGER_TAG, printer);
        emitDefWord(CgenConstants.DEFAULT_OBJFIELDS + CgenConstants.INT_SLOTS, printer);
        emitDefLabel(ScalaConstants.ObjectClassname + IRLingo.dispatchSuffix, printer);
        emitDefWord(0, printer);
    }
    
    private static void emitBooleanPrototype(java.io.PrintStream printer)
    {
        printer.println(ScalaConstants.BooleanClassname + IRLingo.protoSuffix + ":");
        emitDefWord(CgenConstants.BOOLEAN_TAG, printer);
        emitDefWord(CgenConstants.DEFAULT_OBJFIELDS + CgenConstants.BOOL_SLOTS, printer);
        emitDefLabel(ScalaConstants.ObjectClassname + IRLingo.dispatchSuffix, printer);
        emitDefWord(0, printer);
    }
    
    private static void emitStringPrototype(java.io.PrintStream printer)
    {
        printer.println(ScalaConstants.StringClassname + IRLingo.protoSuffix + ":");
        emitDefWord(CgenConstants.STRING_TAG, printer);
        emitDefWord(CgenConstants.DEFAULT_OBJFIELDS + CgenConstants.STRING_SLOTS + 1, printer);
        emitDefLabel(ScalaConstants.StringClassname + IRLingo.dispatchSuffix, printer);
        String label = CgenConstants.intConstPrefix + ScalaTreeChecker.integerTable.get(0);
        emitDefLabel(label, printer);
        emitDefStorage(4, printer);
    }
    
    private static void emitConsolePrototype(java.io.PrintStream printer)
    {
        printer.println(ScalaConstants.ConsoleClassname + IRLingo.protoSuffix + ":");
        emitDefWord(CgenConstants.CONSOLE_TAG, printer);
        emitDefWord(CgenConstants.DEFAULT_OBJFIELDS, printer);
        emitDefLabel(ScalaConstants.ConsoleClassname + IRLingo.dispatchSuffix, printer);
    }
    
    private static void emitPrototype(ScalaClassLayout layout, java.io.PrintStream printer)
    {
        printer.println(layout.name + IRLingo.protoSuffix + ":");
        emitDefWord(layout.tag, printer);
        emitDefWord(CgenConstants.DEFAULT_OBJFIELDS + layout.attrOffset.size(), printer);
        emitDefLabel(layout.name + IRLingo.dispatchSuffix, printer);
        for (int attrIdx = 0; attrIdx < layout.attrOffset.size(); attrIdx++)
            emitDefWord(0, printer);
    }
    
    private class ClassComp implements java.util.Comparator<Object> {
        public int compare(Object left, Object right) {
            ScalaClassLayout lhsLayout = (ScalaClassLayout) left;
            ScalaClassLayout rhsLayout = (ScalaClassLayout) right;
            return lhsLayout.tag - rhsLayout.tag;
        }
    }
    
    private class CgenMethodInfoPair extends ScalaClassLayout.CgenMethodInfo
    {
        String methodName;
        
        CgenMethodInfoPair(ScalaClassLayout.CgenMethodInfo info)
        {
            super(info.offset, info.className);
        }
    }
    
    void code(java.io.PrintStream printer)
    {
        printer.println("#### data section");
        printer.println("\t.data");
        printer.println("#### object tags");
        printer.println("_int_tag:");
        printer.println("\tDW\t1");
        printer.println("_bool_tag:");
        printer.println("\tDW\t2");
        printer.println("_string_tag:");
        printer.println("\tDW\t3");
        printer.println("#### constants");
        for (String str: ScalaTreeChecker.stringTable.keySet())
            emitStringConstant(str, printer);
        for (Integer value: ScalaTreeChecker.integerTable.keySet())
            emitIntegerConstant(value, printer);
        emitBoolConstant(false, printer);
        emitBoolConstant(true, printer);
        printer.println("#### class names by tag");
        printer.println(IRLingo.classNameTable + ":");
        Vector<ScalaClassLayout> sortedLayouts = new Vector<ScalaClassLayout>();
        sortedLayouts.addAll(ctx.getClassLayouts());
        Collections.sort(sortedLayouts, new ClassComp());
        for (ScalaClassLayout layout: sortedLayouts) {
            int idx = ScalaTreeChecker.stringTable.get(layout.name);
            String label = CgenConstants.strConstPrefix + idx;
            emitDefLabel(label, printer);
        }
        printer.println("#### prototypes and init by tag");
        printer.println(IRLingo.classObjInfoTable + ":");
        for (ScalaClassLayout layout: sortedLayouts) {
            String label = layout.name + IRLingo.protoSuffix;
            emitDefLabel(label, printer);
            label = layout.name + IRLingo.initSuffix;
            emitDefLabel(label, printer);
        }
        printer.println("#### dispatch tables");
        for (ScalaClassLayout layout: sortedLayouts) {
            String label = layout.name + IRLingo.dispatchSuffix;
            printer.println(label + ":");
            CgenMethodInfoPair[] methodByIdx =
                new CgenMethodInfoPair[layout.methodOffset.size()];
            for (String methodName: layout.methodOffset.keySet()) {
                ScalaClassLayout.CgenMethodInfo info = layout.methodOffset.get(methodName);
                methodByIdx[info.offset] = new CgenMethodInfoPair(info);
                methodByIdx[info.offset].methodName = methodName;
            }
            for (CgenMethodInfoPair info: methodByIdx)
                emitDefLabel(info.className + "." + info.methodName, printer);
        }
        printer.println("#### prototype objects");
        emitObjectPrototype(printer);
        emitIntegerPrototype(printer);
        emitBooleanPrototype(printer);
        emitStringPrototype(printer);
        emitConsolePrototype(printer);
        for (ScalaClassLayout layout: sortedLayouts)
            if (!LayoutBuilder.isRuntimeClass(layout.name))
                emitPrototype(layout, printer);
    }
}
